#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<sys/select.h>
#include<sys/epoll.h>
#include <fcntl.h>
#include <errno.h>


#define LISTEN_NUM 10


void error_handing(char *message)
{
    fputs(message,stderr);
    fputc('\n',stderr);
    exit(1);
}
int main(int argc, char const *argv[])
{
    //步骤1：创建套接字
    int server_sock;
    server_sock = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
    if (server_sock == -1)
    {
        error_handing("socket() error");
    }
    
    //步骤2：绑定监听端口
    struct sockaddr_in server_addr;
    memset(&server_addr,0,sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(atoi("3333"));
    if (bind(server_sock,(struct sockaddr *)&server_addr,sizeof(server_addr)) == -1)
    {
        error_handing("bind() error");
    }

    //步骤3：开启监听
    if (listen(server_sock,LISTEN_NUM) == -1)
    {
        error_handing("listen() error");
    }

    //步骤4：做epoll_create()调用之前的准备

    int epoll_fd;//epoll实例
    struct epoll_event *epoll_events;//用于接收激活链表中的事件
    struct epoll_event epoll_event;//用于加入事件
    const int events_num = 999;
    epoll_events = (struct epoll_event *)malloc(sizeof(struct epoll_event) * events_num);

    //步骤5：调用epoll_create()初始化epoll实例
    epoll_fd = epoll_create(1); //可以忽略这个参数，填入的参数为操作系统参考
   
    // 将服务器套接字加入 epoll 监听
    epoll_event.events = EPOLLIN; //需要读取数据的情况
    epoll_event.data.fd = server_sock;

    //例程epoll_fd中添加文件描述符server_sock，之后epoll_fd就会监听server_sock的EPOLLIN事件
    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_sock, &epoll_event); 

    int event_count;//激活事件计数
    while (1)
    {
        //步骤5：调用epoll_wait()函数等待激活事件出现
        event_count = epoll_wait(epoll_fd, epoll_events, events_num, -1); //获取改变了的文件描述符，返回数量,-1表示永久等待
        if (event_count == -1)
        {
            printf("epoll_wait() error\n");
            break;
        }

        //步骤6：处理激活事件
        for (size_t i = 0; i < event_count; i++)
        {
            //如果是服务器套接字的事件
            if (epoll_events[i].data.fd == server_sock)//客户端请求连接时
            {

                struct sockaddr client_addr;
                int client_addr_size = sizeof(client_addr);
                int client_sock;
                // 接受客户端连接
                client_sock = accept(server_sock, (struct sockaddr *)&client_addr, &client_addr_size);

                epoll_event.events = EPOLLIN;
                //epoll_event.events = EPOLLIN | EPOLLET;设置为边缘触发模式

                //设置为非阻塞模式
                // int flags = fcntl(client_sock, F_GETFL, 0);
                // if (fcntl(client_sock, F_SETFL, flags | O_NONBLOCK) == -1) {
                //     perror("fcntl()");
                //     exit(EXIT_FAILURE);
                // }

                epoll_event.data.fd = client_sock; //把客户端套接字添加进去
                epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_sock, &epoll_event);
                printf("Connected client... %d \n", client_sock);
            }
            else //是客户端套接字时
            {
                const int message_size =30;
                char message[message_size];
                int read_nums = read(epoll_events[i].data.fd, message, message_size);
                if (read_nums == 0)
                {
                    epoll_ctl(epoll_fd, EPOLL_CTL_DEL, epoll_events[i].data.fd, NULL); //从epoll中删除套接字
                    close(epoll_events[i].data.fd);
                    printf("Closed client : %d \n", epoll_events[i].data.fd);
                }
                else // 读取数据并回传给客户端
                {
                    printf("Receive message is %s\n",message);
                    write(epoll_events[i].data.fd, message, read_nums);
                }
            }
        }
    }
    //步骤7：关闭套接字和epoll实例
    close(server_sock);
    close(epoll_fd);
 
    return 0;
}
